﻿using System;
using System.Collections.Generic;
using System.Text;
using BMC.ARSystem;
using System.Reflection;
using System.Collections;

namespace NRemedy
{
    public class ARServer : IARServer
    {
        private bool isLogin { get; set; }
        private Server _server;

        public BMC.ARSystem.Server Server
        {
            get
            {
                if (_server == null)
                    _server = new Server();
                return _server;
            }
 
        }

        public bool LogIn(ARLogInRequestContext loginInfo)
        {
            bool isLogin = false;
            if(loginInfo.IsMinimunParameter())
            {
                try
                {
                    Server.Login(loginInfo.Server, loginInfo.UserName, loginInfo.Password, loginInfo.Authentication);
                    isLogin = true;
                }
                catch (ARException are)
                {
                    throw are;
                }
            }
            return isLogin;
        }

        public void LogOut()
        {
            Server.Logout();
        }


        public IEnumerable<BMC.ARSystem.ARForm> GetListForm(ARFormFilterDelegate formFilter)
        {
            ArrayList list = Server.GetListForm();
            foreach (string formName in list){
                BMC.ARSystem.ARForm form = Server.GetForm(formName);
                if (formFilter != null){
                    if (!formFilter(form))
                        continue;
                    yield return form;                        
                }
                else
                    yield return form;
            }
        }


        public IEnumerable<BMC.ARSystem.Field> GetListField(string formName,ARFieldFilterDelegate fieldFilter)
        {
            //Constants.AR_FIELD_TYPE_ALL
            if (string.IsNullOrEmpty(formName)) throw new ArgumentNullException("formName");
            ArrayList list = Server.GetListField(formName);
            foreach (uint fieldID in list){
                BMC.ARSystem.Field field = Server.GetField(formName, fieldID);
                if(fieldFilter == null)
                    yield return field;
                if (!fieldFilter(field))
                    continue;
                yield return field;
            }
        }

        /// <summary>
        /// login first
        /// </summary>
        /// <param name="entry"></param>
        /// <returns>reqeust id</returns>
        public string CreateEntry(ARForm entry)
        {
            if (entry == null) return null;
            string formName = ResolveFormName(entry);
            FieldValueList list = new FieldValueList();
            foreach (var arf in ResolveFormField(entry)){
                if(arf != null)
                    list.Add(arf.DatabaseID, arf.Value);
            }
            return Server.CreateEntry(formName, list);
 
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="entry">entry should contains request id key</param>
        public void GetEntry(ARForm entry)
        {
            if(entry == null)
                throw new ArgumentNullException("entry");
            if(string.IsNullOrEmpty(entry.DefaultEntryKey))
                throw new NullReferenceException("entry.DefaultEntryKey should not be empty");
            string formName = ResolveFormName(entry);

            FieldValueList resultList = Server.GetEntry(formName, entry.DefaultEntryKey);

            Type entryType = entry.GetType();
            PropertyInfo[] properties = entryType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetField);
            Dictionary<uint, PropertyInfo> dic = new Dictionary<uint, PropertyInfo>();
            foreach (var p in properties){
                ARField field = GetFieldInfoFromPropertyInfo(entry, p);
                if (field == null) continue;
                dic.Add(field.DatabaseID, p);
            }

            PropertyInfo pi;
            foreach (uint key in resultList.Keys){
                if (!dic.TryGetValue(key, out pi)) continue;
                try
                {
                    object value = resultList[key];
                    if (value.GetType() == typeof(System.DBNull)) 
                        value = null;
                    pi.SetValue(entry, value, null);
                }
                catch
                {
                    continue;
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="entry">entry should contains request id key</param>
        /// <param name="set"></param>
        public void SetEntry(ARForm entry,FieldValueSet set)
        {
            if (entry == null) throw new ArgumentNullException("entry");
            if (set == null) throw new ArgumentNullException("set");
            if (string.IsNullOrEmpty(entry.DefaultEntryKey)) throw new ArgumentNullException("DefaultEntryKey", "entry.DefaultEntryKey must not null or empty.");
            string formName = ResolveFormName(entry);
            FieldValueList list = new FieldValueList();
            foreach (var item in set)
            {
                var field = GetFieldInfoFromEntity(entry, item.Key);
                if (field == null) continue;
                list.Add(field.DatabaseID, item.Value);
            }
            Server.SetEntry(formName, entry.DefaultEntryKey, list);
        }

        public string ResolveFormName(ARForm entry)
        {
            if (entry == null) throw new ArgumentNullException("entry");
            Type FormActualType = entry.GetType();
            object[] attributes = FormActualType.GetCustomAttributes(typeof(ARFormMappingAttribute), false);
            if (attributes.Length == 0) return FormActualType.Name;
            return (attributes[0] as ARFormMappingAttribute).FormName;
        }

        public IEnumerable<ARField> ResolveFormField(ARForm entry)
        {
            if (entry == null) throw new ArgumentNullException("entry");
            Type FormActualType = entry.GetType();
            PropertyInfo[] properties = FormActualType.GetProperties(BindingFlags.Public | BindingFlags.GetProperty | BindingFlags.Instance);
            if(properties.Length == 0) return null;
            return PropertyFilter(entry,properties);
        }

        private IEnumerable<ARField> PropertyFilter(ARForm entry,PropertyInfo[] properties)
        {
            List<uint> dbIds = new List<uint>();
            foreach (PropertyInfo pi in properties){
                ARField field = GetFieldInfoFromPropertyInfo(entry, pi);
                if (field != null){
                    if (dbIds.Contains(field.DatabaseID)) throw new DuplicateDatabaseIDException(field.DatabaseID);
                    dbIds.Add(field.DatabaseID);
                    yield return field;
                }
            }
        }

        public ARField GetFieldInfoFromEntity(ARForm entity, string propertyName)
        {
            if (entity == null) throw new ArgumentNullException("entry");
            if (string.IsNullOrEmpty(propertyName)) throw new ArgumentNullException("propertyName");
            Type FormActualType = entity.GetType();
            PropertyInfo property = FormActualType.GetProperty(propertyName,BindingFlags.Public | BindingFlags.GetProperty | BindingFlags.Instance);
            if (property == null) throw new Exception(string.Format("{0} not found.",propertyName));
            return GetFieldInfoFromPropertyInfo(entity, property);
        }

        private ARField GetFieldInfoFromPropertyInfo(ARForm entity, PropertyInfo property)
        {
            if (property == null) throw new ArgumentNullException("property");
            object[] attributes = property.GetCustomAttributes(typeof(ARFieldMappingAttribute), true);
            if (attributes.Length == 0) return null;
            ARFieldMappingAttribute attribute = attributes[0] as ARFieldMappingAttribute;
            ARField field = new ARField();
            field.Value = DefaultValueResolver(entity, property);
            field.DatabaseID = attribute.DatabaseID;
            field.PropertyName = property.Name;
            return field;
        }

        private object DefaultValueResolver(ARForm entity, PropertyInfo property)
        {
            object value = property.GetValue(entity, null);
            if (value == null)
            {
                if (property.PropertyType == typeof(String))
                    return string.Empty;
                else if (property.PropertyType == typeof(ValueType))
                    return 0;
                else
                    return null;
            }
            else
                return value;
 
        }

    }
}
